package com.apobates.jforum.grief.aurl.utils.defend;

import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;

/**
 * 校验条件链
 * @param <T> 判断的目标类型
 */
public class PredicateQueue<T> {
    private final AssertPredicate<T>[] conditions;

    /**
     * 创建空条件链
     */
    private PredicateQueue(){
        this.conditions = new AssertPredicate[]{};
    }

    /**
     * 使用指定条件链初始化
     * @param conditions
     */
    private PredicateQueue(AssertPredicate<T>[] conditions){
        this.conditions = conditions;
    }

    /**
     * 使用构徤器初始化
     */
    private PredicateQueue(Builder<T> builder){
        this.conditions = builder.conditions;
    }

    /**
     * 返回所有校验条件
     * @return
     */
    public List<AssertPredicate<T>> getConditions(){
        return (hasCondition()) ? List.of(this.conditions) : Collections.emptyList();
    }

    /**
     * 是否存在条件定义
     * @return 不存在返回false,存在返回true
     */
    public boolean hasCondition(){
        return getConditionSize() > 0;
    }

    /**
     * 返回条件定义的数量
     * @return
     */
    public int getConditionSize(){
        return this.conditions.length;
    }
    /**
     * 链接另一个校验条件链中的规则
     * @param predicateQueue
     * @return
     */
    public PredicateQueue<T> contact(PredicateQueue<T> predicateQueue){
        if(null!= predicateQueue && predicateQueue.hasCondition()){
            AssertPredicate<T>[] result = Arrays.copyOf(this.conditions, this.getConditionSize() + predicateQueue.getConditionSize());
            System.arraycopy(predicateQueue.getConditions().toArray(), 0, result, this.getConditionSize(), predicateQueue.getConditionSize());
            return new PredicateQueue<>(result);
        }
        return this;
    }

    /**
     * 创建防卫检查
     * @param assertion
     * @return
     */
    public DefendAssert<T> asserts(Assertion<T> assertion){
        return new DefendAssert<>(assertion, this);
    }

    /**
     * 创建一个基于notNull的校验条件链构建器
     * @return
     * @param <T>
     */
    public static <T> Builder<T> require(){
        Builder<T> ins = new Builder<>();
        ins.add(Predicates.notNull(null));
        return ins;
    }

    /**
     * 创建一个基于notNull的校验条件链构建器
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> Builder<T> require(String message){
        Builder<T> ins = new Builder<>();
        ins.add(Predicates.notNull(message));
        return ins;
    }

    /**
     * 创建一个基于自定义规则的校验条件链构建器
     * @param predicate 自定义规则的谓词函数据
     * @return
     * @param <T>
     */
    public static <T> Builder<T> predicate(Predicate<T> predicate){
        Builder<T> ins = new Builder<>();
        if(null!=predicate){
            return ins.add(Predicates.predicate(null, predicate, null));
        }
        return ins;
    }

    /**
     * 创建一个基于自定义规则的校验条件链构建器
     * @param predicate 自定义规则的谓词函数据
     * @param message 失败的消息
     * @return
     * @param <T>
     */
    public static <T> Builder<T> predicate(Predicate<T> predicate, String message){
        Builder<T> ins = new Builder<>();
        if(null!=predicate){
            return ins.add(Predicates.predicate(null, predicate, message));
        }
        return ins;
    }

    /**
     * 创建一个基于自定义规则的校验条件链构建器
     * @param condition AssertPredicate的实现类
     * @return
     * @param <T>
     */
    public static <T> Builder<T> define(AssertPredicate<T> condition){
        Builder<T> ins = new Builder<>();
        if(null!=condition) {
            return ins.add(condition);
        }
        return ins;
    }

    /**
     * 创建一个校验条件链构建器
     * @return
     * @param <T>
     */
    public static <T> Builder<T> define(){
        Builder<T> ins = new Builder<>();
        return ins;
    }

    /**
     * 创建空校验条件链
     * @return
     * @param <T>
     */
    public static <T> PredicateQueue<T> empty(){
        return new PredicateQueue<>();
    }

    public static class Builder<T>{
        private AssertPredicate<T>[] conditions;

        private Builder(){
            this.conditions = new AssertPredicate[]{};
        }

        private Builder<T> add(AssertPredicate<T> predicate){
            this.addElement(predicate);
            return this;
        }

        /**
         * 设置非空判断条件, 非空表示成功, 空表示失败。
         * 适用于: Collection实现,String,Optional
         * @param message 失败的消息
         * @return
         */
        public Builder<T> notEmpty(String message){
            this.addElement(Predicates.notEmpty(message));
            return this;
        }

        /**
         * 设置非空判断条件, 非空表示成功, 空表示失败。默认的失败消息: 集合不存在可用的元素
         * 适用于: Collection实现,String,Optional
         * @return
         */
        public Builder<T> notEmpty(){
            return this.notEmpty(null);
        }

        /**
         * 设置包含判断条件, 包含表示成功, 不包含表示失败。
         * 注意: 若T表示Collection或Map时相当于containsAll
         * 适用于: Collection实现, Map实现, String
         * @param message 失败的消息
         * @param element 判断的元素
         * @return
         */
        public Builder<T> contains(String message, T element){
            this.addElement(Predicates.contains(message, element));
            return this;
        }

        /**
         * 设置包含判断条件, 包含表示成功, 不包含表示失败。
         * 注意: 若T表示Collection或Map时相当于containsAll
         * 适用于: Collection实现, Map实现, String
         * @param element 判断的元素
         * @return
         */
        public Builder<T> contains(T element){
            return this.contains(null, element);
        }

        /**
         * 设置等于判断条件, 等于表示成功, 不等于表示失败。
         * 适用于: Boolean, Byte, Character, String, Number, Enum
         * @param message 失败的消息
         * @param value 判断的目标
         * @return
         */
        public Builder<T> equalsTo(String message, T value){
            this.addElement(Predicates.equalsTo(message, value));
            return this;
        }

        /**
         * 设置等于判断条件, 等于表示成功, 不等于表示失败。
         * 适用于: Boolean, Byte, Character, String, Number, Enum
         * @param value
         * @return
         */
        public Builder<T> equalsTo(T value){
            return this.equalsTo(null, value);
        }

        /**
         * 设置正则表达式匹配判断条件, 匹配表示成功, 不匹配表示失败。
         * 适用于: String
         * @param message 失败的消息
         * @param pattern 正则表达式
         * @return
         */
        public Builder<T> matchTo(String message, Pattern pattern){
            this.addElement(Predicates.matchTo(message, pattern));
            return this;
        }

        /**
         * 设置正则表达式匹配判断条件, 匹配表示成功, 不匹配表示失败。
         * 适用于: String
         * @param pattern 正则表达式
         * @return
         */
        public Builder<T> matchTo(Pattern pattern){
            return this.matchTo(null, pattern);
        }

        /**
         * 设置无符号正整数判断条件, 是表示成功, 不是表示失败。
         * 适用于: String, Number
         * @param message 失败的消息
         * @return
         */
        public Builder<T> unsigned(String message){
            this.addElement(Predicates.unsigned(message));
            return this;
        }

        /**
         * 设置无符号正整数判断条件, 是表示成功, 不是表示失败。
         * 适用于: String, Number
         * @return
         */
        public Builder<T> unsigned(){
            return this.unsigned(null);
        }

        /**
         * 设置大于0的正整数判断条件, 是表示成功, 不是表示失败。
         * 适用于: String, Number
         * @param message 失败的消息
         * @return
         */
        public Builder<T> positive(String message){
            this.addElement(Predicates.positive(message));
            return this;
        }

        /**
         * 设置大于0的正整数判断条件, 是表示成功, 不是表示失败。
         * 适用于: String, Number
         * @return
         */
        public Builder<T> positive(){
            return this.positive(null);
        }

        /**
         * 设置范围值的判断, 在范围中表示成功, 不在范围中表示失败。
         * 适用于: String, Character, Number
         * @param message 失败的消息
         * @param start 开始值，在范围内
         * @param finish 结束值，在范围内
         * @return
         */
        public Builder<T> range(String message, T start, T finish){
            this.addElement(Predicates.range(message, start, finish));
            return this;
        }

        /**
         * 设置等于0判断。 等于表示成功, 不等于表示失败。
         * 适用于: String, Number
         * @param message 失败的消息
         * @return
         */
        public Builder<T> zero(String message){
            this.addElement(Predicates.zero(message));
            return this;
        }

        /**
         * 设置等于0判断。 等于表示成功, 不等于表示失败。
         * 适用于: String, Number
         * @return
         */
        public Builder<T> zero(){
            return this.zero(null);
        }

        /**
         * 设置是否是数字。满足表示成功, 不满足表示失败。
         * 适用于: String
         * @param message 失败的消息
         * @return
         */
        public Builder<T> numeric(String message){
            this.addElement(Predicates.numeric(message));
            return this;
        }

        /**
         * 设置是否是数字。满足表示成功, 不满足表示失败。
         * 适用于: String
         * @return
         */
        public Builder<T> numeric(){
            return this.numeric(null);
        }

        /**
         * 设置是否是字母组合。满足表示成功, 不满足表示失败。
         * 适用于: String
         * @param message 失败的消息
         * @return
         */
        public Builder<T> alpha(String message){
            this.addElement(Predicates.alpha(message));
            return this;
        }

        /**
         * 设置是否是字母组合。满足表示成功, 不满足表示失败。
         * 适用于: String
         * @return
         */
        public Builder<T> alpha(){
            return this.alpha(null);
        }

        /**
         * 金额判断。 满足数字表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @param message 失败的消息
         * @return
         */
        public Builder<T> decimal(String message){
            this.addElement(Predicates.decimal(message));
            return this;
        }

        /**
         * 金额判断。 满足数字表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @return
         */
        public Builder<T> decimal(){
            return this.decimal(null);
        }

        /**
         * 设置金额判断。 满足数字且小数位符合表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @param message 失败的消息
         * @param scale 范围/小数位长度
         * @return
         */
        public Builder<T> decimalScale(String message, int scale){
            this.addElement(Predicates.decimalScale(message, scale));
            return this;
        }

        /**
         * 设置金额判断。 满足数字且小数位符合表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @param scale 范围/小数位长度
         * @return
         */
        public Builder<T> decimalScale(int scale){
            return this.decimalScale(null, scale);
        }

        /**
         * 设置金额判断。满足数字且小数位符合表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @param message 失败的消息
         * @param precision 精度/数字总长度
         * @param scale 范围/小数位长度
         * @return
         */
        public Builder<T> decimalFull(String message, int precision, int scale){
            this.addElement(Predicates.decimalFull(message, precision, scale));
            return this;
        }

        /**
         * 设置金额判断。满足数字且小数位符合表示成功, 不满足表示失败。
         * 适用于: String, Float, Double, BigDecimal
         * @param precision 精度/数字总长度
         * @param scale 范围/小数位长度
         * @return
         */
        public Builder<T> decimalFull(int precision, int scale){
            return this.decimalFull(null, precision, scale);
        }

        /**
         * 过去时间判断。满足时间且小于当前时区时间表示成功, 不满足表示失败。
         * 适用于: String
         * @param message 失败的消息
         * @param format 日期的格式
         * @return
         */
        public Builder<T> pastStr(String message, String format){
            this.addElement(Predicates.pastStr(message, format));
            return this;
        }

        /**
         * 设置过去时间判断。满足时间且小于当前时区时间表示成功, 不满足表示失败。
         * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
         * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
         * @param message 失败的消息
         * @return
         */
        public Builder<T> past(String message){
            this.addElement(Predicates.past(message));
            return this;
        }

        /**
         * 设置过去时间判断。满足时间且小于当前时区时间表示成功, 不满足表示失败。
         * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
         * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
         * @return
         */
        public Builder<T> past(){
            return this.past(null);
        }

        /**
         * 未来时间判断。满足时间且大于当前时区时间表示成功, 不满足表示失败。
         * 适用于: String
         * @param message 失败的消息
         * @param format 日期的格式
         * @return
         */
        public Builder<T> futureStr(String message, String format){
            this.addElement(Predicates.futureStr(message, format));
            return this;
        }

        /**
         * 设置未来时间判断。满足时间且大于当前时区时间表示成功, 不满足表示失败。
         * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
         * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
         * @param message 失败的消息
         * @return
         */
        public Builder<T> future(String message){
            this.addElement(Predicates.future(message));
            return this;
        }

        /**
         * 设置未来时间判断。满足时间且大于当前时区时间表示成功, 不满足表示失败。
         * 注意: 若T是字符串时要求符合: yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd 格式
         * 适用于: String, LocalDate, LocalDateTime, ZonedDateTime, Instant
         * @return
         */
        public Builder<T> future(){
            return this.future(null);
        }

        /**
         * 创建防卫检查器
         * @param assertion 校验断言
         * @return
         */
        public DefendAssert<T> asserts(Assertion<T> assertion){
            return new DefendAssert<>(assertion, new PredicateQueue<>(this));
        }

        /**
         * 创建校验条件链
         * @return
         */
        public PredicateQueue<T> build(){
            return new PredicateQueue<>(this);
        }

        private int addElement(AssertPredicate<T> condition){
            // 最后一位
            int lastEleIndex = this.conditions.length;
            // 为数组扩容
            AssertPredicate<T>[] result = Arrays.copyOf(this.conditions, lastEleIndex + 1);
            this.conditions = result;
            this.conditions[lastEleIndex] = condition;
            return lastEleIndex+1;
        }
    }
}
